package org.zfin.properties.ant;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Property;

import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

/**
 * Loads property file into ant an enum if passed in.
 * <p/>
 * <loadProperties file="(flat property file name)" enumClass="(class name)"/>
 * <p/>
 * <loadProperties />
 * <p/>
 * Arguments:
 * - propertyFile (required)
 * - enumClass (optional: name of enum to load into.)
 * - override  (optional: default is true)
 */
public class LoadPropertiesTask extends PropertyFileTask {

    private final static Logger logger = Logger.getLogger(LoadPropertiesTask.class);
    protected final static String DEFAULT_INSTANCE_PROPERTIES_FILE = "default-instance.properties";
    protected final static String DEFAULT_FILE_PROPERTY = "PROPERTY_FILE";
    protected final static String DEFAULT_ENUM_PROPERTY = "ENUM_CLASS";
    private PropertiesConfiguration propertiesConfiguration;

    private boolean processSystemProperties = true ;
    private boolean overrideSystemProperties = false ;
    private boolean useDefaultProperties = false ;

    public void execute() {
        try {
            logger.debug("Loading property file: " + getFile());
            if (getEnumClass() != null) {
                logger.debug("Into class: " + getEnumClass());
            }
            File propertyFile = findPropertyFile();
            if (propertyFile == null) {
                // use default instead:
                throw new RuntimeException("Property file does not exist: " + getFile());
            }
            setFile(propertyFile.getAbsolutePath());
            propertiesConfiguration = createPropertiesConfiguration();
            Project project = getProject();
            // process ant
            if (project != null) {
                processAnt();
            }

            if (getEnumClass() != null) {
                processEnums(getEnumClass());
            }

            if(processSystemProperties || overrideSystemProperties){
                processSystemProperties();
            }

            if(useDefaultProperties){
                try {
                    createDefaultPropertiesFile();
                } catch (Throwable t) {
                    logger.error("Failed to create default properties",t);
                }
            }
        } catch (ConfigurationException e) {
            throw new RuntimeException("failed to load configuration", e);
        }
    }

    private void processEnums(String enumclass) {
        try {
            Class aClass = Class.forName(enumclass);
            if (!aClass.isEnum()) {
                throw new RuntimeException("Class should be an enum: " + enumclass);
            }
            SettableEnum[] enums = (SettableEnum[]) aClass.getEnumConstants();

            for (SettableEnum anEnum : enums) {
                List<String> values = propertiesConfiguration.getList(anEnum.name());
                String value = (CollectionUtils.isNotEmpty(values) ? values.get(0) : null);
                if (value != null) {
                    // populate enum
                    anEnum.setValue(value);
                } else if (anEnum.value() == null) {
                    logger.error("value is undefined: " + anEnum.name());
                    //                    return ;
                }

                if (anEnum.value() == null || anEnum.value().startsWith("$")) {
                    logger.debug(anEnum.name() + " " + anEnum.value());
                }
            }
        } catch (ClassNotFoundException e) {
            logger.error("Problem processing class: " + enumclass, e);
        }
    }

    private void processAnt() {
        String key;
        for (Iterator iterator = propertiesConfiguration.getKeys(); iterator.hasNext();) {
            key = iterator.next().toString();
            Property property = (Property) getProject().createTask("property");
            property.setName(key);
            property.setValue(propertiesConfiguration.getString(key));
            property.execute();
        }
    }


    private void processSystemProperties() {
        String key;
        for (Iterator iterator = propertiesConfiguration.getKeys(); iterator.hasNext();) {
            key = iterator.next().toString();
            if(overrideSystemProperties || System.getProperty(key)==null){
                System.setProperty(key,propertiesConfiguration.getString(key)) ;
            }
        }
    }

    protected void createDefaultPropertiesFile() {
        BufferedWriter bufferedWriter = null;
        try {
            bufferedWriter = new BufferedWriter(new FileWriter(DEFAULT_INSTANCE_PROPERTIES_FILE));
            File file = new File(getFile());
            String fileName = file.getAbsolutePath();
            fileName = fileName.replace("\\", "/");
            bufferedWriter.write(DEFAULT_FILE_PROPERTY + "=" + fileName);
            bufferedWriter.close();
        } catch (IOException e) {
            logger.error("Failed to write default property file: " + DEFAULT_INSTANCE_PROPERTIES_FILE, e);
        } finally {
            try {
                if(bufferedWriter!=null){
                    bufferedWriter.close();
                }
            } catch (IOException e) {
                logger.error(e);
            }
        }
    }

    protected void deleteDefaultPropertiesFile() {
        File defaultFile = new File(DEFAULT_INSTANCE_PROPERTIES_FILE);
        if (defaultFile.exists()) {
            defaultFile.delete();
        }
    }

    protected File findPropertyFile() {
        String fileName;
        // <loadProperties file=(path to property file)
        fileName = getFile();
        if (fileExists(fileName)) return new File(fileName);

        // ant -DPROPERTY_FILE=(path to property file)
        fileName = System.getProperty(DEFAULT_FILE_PROPERTY);
        if (fileExists(fileName)) return new File(fileName);

        // load from file at root: default-instance.properties
        fileName = loadFromDefaultPropertiesFile();
        if (fileExists(fileName)) return new File(fileName);

        if(useDefaultProperties){
            // load from system property: PROPERTY_FILE
            fileName = System.getenv(DEFAULT_FILE_PROPERTY);
            if (fileExists(fileName)) return new File(fileName);
        }
        return null;
    }

    private boolean fileExists(String fileName) {
        if (StringUtils.isEmpty(fileName)) return false;
        return new File(fileName).exists();
    }

    private String loadFromDefaultPropertiesFile() {
        Properties properties = new Properties();
        try {
            File file = new File(DEFAULT_INSTANCE_PROPERTIES_FILE);
            if (file.exists()) {

                properties.load(new FileReader(file));
                if (System.getProperty("sun.desktop") != null && System.getProperty("sun.desktop").equals("windows")){
                    for(Object key :properties.keySet()){
                        String value = (String) properties.get(key);
                        properties.setProperty((String) key, value.replace("\\", "/"));
                    }
                }
                return properties.getProperty(DEFAULT_FILE_PROPERTY);
            } else {
                logger.warn("Default property file (" + DEFAULT_INSTANCE_PROPERTIES_FILE + ") not exist using environment one");
            }
        } catch (IOException e) {
            logger.error("failed to read property file: " + DEFAULT_INSTANCE_PROPERTIES_FILE + "  using environment definition", e);
        }
        return null;
    }

    public boolean isProcessSystemProperties() {
        return processSystemProperties;
    }

    public void setProcessSystemProperties(boolean processSystemProperties) {
        this.processSystemProperties = processSystemProperties;
    }

    public boolean isOverrideSystemProperties() {
        return overrideSystemProperties;
    }

    public void setOverrideSystemProperties(boolean overrideSystemProperties) {
        this.overrideSystemProperties = overrideSystemProperties;
    }

    public boolean isUseDefaultProperties() {
        return useDefaultProperties;
    }

    public void setUseDefaultProperties(boolean useDefaultProperties) {
        this.useDefaultProperties = useDefaultProperties;
    }
}
